const Web3 = require('web3');
import BigNumber from 'bignumber.js';

const TransferGasLimit = {
  ethGasLimit: 25200,
  tokenGasLimit: 110000,
  extraFuncGasLimit: 600000,
  swapGasLimit: 100000,
};
import {erc20Abi, swhtAbi} from './contract';

export default class ChainUtils {
  /**
   * 初始化chain交互sdk
   * @param {*} nodeUrl
   * @param {*} config
   */
  constructor(nodeUrl) {
    if (nodeUrl) {
      this.web3 = new Web3(new Web3.providers.HttpProvider(nodeUrl));
    }

    //用于监听的交易
    this.listenTrxs = [];
  }

  setUrl(nodeUrl) {
    console.log('初始化web3');
    console.log(nodeUrl);
    this.web3 = new Web3(new Web3.providers.HttpProvider(nodeUrl));
  }

  static TransferGasLimit = {
    ...TransferGasLimit,
  };

  /**
   * 保留小数
   * @param {*} a
   * @param {*} num
   * @returns
   */
  static subStringNum(a, num) {
    if (a < 0.000000000001) {
      a = 0;
    }

    BigNumber.config({EXPONENTIAL_AT: 1e9});
    return new BigNumber(a + '').decimalPlaces(num).toString();

    // a = a + '';
    // var aArr = a.split('.');

    // if (aArr.length > 1) {
    //   a = aArr[0] + '.' + aArr[1].substr(0, num);
    // }
    // if (a == 0) return 0;
    // return a;
  }

  addPrivateKey(privateKey) {
    if (this.web3.eth.accounts.wallet.length != 0) {
      this.web3.eth.accounts.wallet.clear();
    }

    this.web3.eth.accounts.wallet.add(privateKey);
  }

  async balanceOf(address) {
    const ether = new BigNumber(Math.pow(10, 18));
    const bigBalance = new BigNumber(await this.web3.eth.getBalance(address));
    let vAmount = parseFloat(bigBalance.dividedBy(ether));
    return ChainUtils.subStringNum(vAmount, 4);
  }

  async currentGas() {
    let price = this.web3.eth.getGasPrice();
    return this.web3.utils.fromWei(price, 'ether');
  }

  /**
   * Send an Eth transaction to the given address with the given amout
   *
   * @param {String} toAddress
   * @param {String} amout 10    单位-ether
   * @param {Number} gasPrice 10000000000  单位-wei
   */
  async sendETHTransaction(toAddress, amout, gasPrice, gasLimit, hashCall) {
    try {
      if (!gasPrice) {
        gasPrice = await this.web3.eth.getGasPrice();
      }

      console.log('gasLimit' + gasLimit);

      const value = this.web3.utils.toWei(amout.toString(), 'ether');
      if (!gasLimit) {
        gasLimit = this.web3.utils.toHex(TransferGasLimit.ethGasLimit);
      }
      const transactionGasPrice = this.web3.utils.toHex(gasPrice);
      const from = this.web3.eth.accounts.wallet[0].address;

      const transactionConfig = {
        to: toAddress,
        value,
        gas: gasLimit,
        gasPrice: transactionGasPrice,
        from,
      };

      console.log(JSON.stringify(transactionConfig, null, 2));

      const cb = await this.web3.eth
        .sendTransaction(transactionConfig)
        .on('transactionHash', (hash) => {
          hashCall(hash);
        })
        .on('error', (err) => {
          hashCall(null, err);
        });
      return cb;
    } catch (err) {
      hashCall(null, err);
      return null;
    }
  }

  async trxInfoQuery() {
    try {
      let queryAction = this.listenTrxs.map((item) =>
        this.web3.eth.getTransactionReceipt(item.hashId),
      );

      this.web3.eth
        .getTransactionReceipt(
          '0xe7518dc101ce0176691a28cb08e3ffc7c082a96a367516758349ef0d00721442',
        )
        .then((res) => {
          console.log('查询交易结果');
          console.log(res);
        });

      let results = await Promise.all(queryAction);
      console.log('查询交易结果');
      console.log(results);

      let nowTime = new Date().valueOf();
      results.forEach((recepit, idx) => {
        let {hashId, success, error, sureTime, date} = this.listenTrxs[idx];

        if (recepit) {
          console.log('监听到交易：' + hashId);

          if (sureTime >= 1) {
            console.log('2次交易确认，回调');
            if (success) {
              success({
                hashId,
                recepit,
              });
            }
            //删除需要监听
            this.listenTrxs.splice(idx, 1);
          } else {
            console.log('交易确认次数不足，继续监听该交易');
            this.listenTrxs[idx].sureTime = sureTime + 1;
          }
        } else if (nowTime - date > 3 * 60 * 1000) {
          //交易3分钟未上链
          if (error) {
            error();
          }
          this.listenTrxs.splice(idx, 1);
        }
      });
    } catch (err) {
      let errStr = err + '';
      console.log('监听交易出现错误' + errStr);
    }

    //如果需要监听交易数量不为0则继续监听
    if (this.listenTrxs.length > 0) {
      console.log('未确认的交易数量：' + this.listenTrxs.length);

      setTimeout(() => {
        this.trxInfoQuery();
      }, 3 * 1000);
    }
  }

  /**
   * 交易监听
   * @param {*} hashId 需要监听的tx hash
   * @param {*} callFunc 回调
   */
  async listenTrx(hashId, success, error) {
    this.listenTrxs.push({
      hashId,
      success,
      error,
      sureTime: 0,
      date: new Date().valueOf(),
    });

    //开始监听
    if (this.listenTrxs.length == 1) {
      this.trxInfoQuery();
    }

    console.log('需监听的交易->' + JSON.stringify(this.listenTrxs, null, 2));
  }

  /**
   * 获取gas价格
   * @returns
   */
  async getGasPrice() {
    return this.web3.eth.getGasPrice();
  }

  /**
   * 计算交易gas费用
   */
  async getTxGasPrice(limit = TransferGasLimit.tokenGasLimit) {
    try {
      let gasPrice = await this.getGasPrice();

      let gas = new BigNumber(gasPrice * limit);
      const ether = new BigNumber(Math.pow(10, 18));
      gas = parseFloat(gas.dividedBy(ether));
      console.log(gas);
      console.log('-------');

      return gas;
    } catch (err) {
      console.log(err);
      console.log('获取gas失败');
      return 0;
    }
  }

  /**
   * 执行合约方法
   * @param {*} abi
   * @param {*} contractAddress
   * @param {*} method 方法名字
   * @param {*} params 参数
   * @param {*} gasPrice
   * @param {*} gasLimit
   * @param {*} hashCall
   * @returns
   */
  async sendContractFunctionTransaction(
    abi,
    contractAddress,
    method,
    params,
    gasPrice,
    gasLimit,
    hashCall,
  ) {
    return this.sendContractFunctionTransactionWithValue(
      abi,
      contractAddress,
      method,
      params,
      gasPrice,
      gasLimit,
      hashCall,
      '0x0',
    );
  }

  /**
   * 执行合约方法
   * @param {*} abi
   * @param {*} contractAddress
   * @param {*} method 方法名字
   * @param {*} params 参数
   * @param {*} gasPrice
   * @param {*} gasLimit
   * @param {*} hashCall
   * @returns
   */
  async getContractFunctionTransaction(
    abi,
    contractAddress,
    method,
    params,
    gasPrice,
    gasLimit,
    from,
  ) {
    return this.getContractFunctionTransactionWithValue(
      abi,
      contractAddress,
      method,
      params,
      gasPrice,
      gasLimit,
      '0x0',
      from,
    );
  }

  /**
   * 执行合约方法
   * @param {*} abi
   * @param {*} contractAddress
   * @param {*} method 方法名字
   * @param {*} params 参数
   * @param {*} gasPrice
   * @param {*} gasLimit
   * @param {*} hashCall
   * @returns
   */
  async sendContractFunctionTransactionWithValue(
    abi,
    contractAddress,
    method,
    params,
    gasPrice,
    gasLimit,
    hashCall,
    value,
  ) {
    try {
      if (!gasPrice) {
        gasPrice = await this.web3.eth.getGasPrice();
      }

      if (!gasLimit) {
        gasLimit = this.web3.utils.toHex(TransferGasLimit.tokenGasLimit);
      }

      console.log('请求参数为');
      console.log(params);

      const contract = new this.web3.eth.Contract(abi, contractAddress);
      const data = contract.methods[method].apply(null, params).encodeABI();
      const from = this.web3.eth.accounts.wallet[0].address;

      const tx = {
        from,
        to: contractAddress,
        value: value,
        data,
        gasLimit: gasLimit,
        gasPrice: this.web3.utils.toHex(gasPrice),
      };

      console.log(JSON.stringify(tx, null, 2));

      const cb = await this.web3.eth
        .sendTransaction(tx)
        .on('transactionHash', (hash) => {
          hashCall && hashCall(hash);
        })
        .on('error', (err) => {
          console.log(err);
          hashCall && hashCall(null);
        });
      return cb;
    } catch (err) {
      console.log(err);
      hashCall && hashCall(null);
      return null;
    }
  }

  /**
   * 获取执行合约交易
   * @param {*} abi
   * @param {*} contractAddress
   * @param {*} method 方法名字
   * @param {*} params 参数
   * @param {*} gasPrice
   * @param {*} gasLimit
   * @param {*} hashCall
   * @returns
   */
  async getContractFunctionTransactionWithValue(
    abi,
    contractAddress,
    method,
    params,
    gasPrice,
    gasLimit,
    value,
    from,
  ) {
    try {
      if (!gasPrice) {
        console.log('查询gasPrice');
        gasPrice = await this.web3.eth.getGasPrice();
      }

      if (!gasLimit) {
        gasLimit = this.web3.utils.toHex(TransferGasLimit.tokenGasLimit);
      }

      console.log('请求参数为');
      console.log(params);

      const contract = new this.web3.eth.Contract(abi, contractAddress);
      const data = contract.methods[method].apply(null, params).encodeABI();

      const tx = {
        from,
        to: contractAddress,
        value: value,
        data,
        gasLimit: gasLimit,
        gasPrice: this.web3.utils.toHex(gasPrice),
      };
      return tx;
    } catch (err) {
      console.log(err);
      return null;
    }
  }

  /**
   * 执行合约的call方法
   * @param {*} abi
   * @param {*} contractAddress
   * @param {*} method
   * @param {*} params
   * @returns
   */
  async sendContractCallTransaction(abi, contractAddress, method, params) {
    const contract = new this.web3.eth.Contract(abi, contractAddress);
    return contract.methods[method].apply(null, params).call();
  }
}
